001 /*
002 * Copyright 2005 Stephen J. McConnell
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.library.impl;
020
021 import java.io.File;
022 import java.io.IOException;
023 import java.net.URI;
024 import java.net.URISyntaxException;
025 import java.text.SimpleDateFormat;
026 import java.util.Arrays;
027 import java.util.ArrayList;
028 import java.util.List;
029 import java.util.Date;
030 import java.util.TimeZone;
031 import java.util.Properties;
032 import java.util.Map;
033 import java.util.Hashtable;
034
035 import net.dpml.lang.Category;
036 import net.dpml.lang.Version;
037
038 import net.dpml.library.Info;
039 import net.dpml.library.Filter;
040 import net.dpml.library.Library;
041 import net.dpml.library.Module;
042 import net.dpml.library.Resource;
043 import net.dpml.library.Type;
044 import net.dpml.library.Data;
045 import net.dpml.library.ResourceNotFoundException;
046 import net.dpml.library.info.InfoDirective;
047 import net.dpml.library.info.TypeDirective;
048 import net.dpml.library.info.ResourceDirective;
049 import net.dpml.library.info.ResourceDirective.Classifier;
050 import net.dpml.library.info.IncludeDirective;
051 import net.dpml.library.info.DependencyDirective;
052 import net.dpml.library.info.AbstractDirective;
053 import net.dpml.library.info.ValidationException;
054 import net.dpml.library.info.FilterDirective;
055 import net.dpml.library.info.Scope;
056
057 import net.dpml.transit.Artifact;
058 import net.dpml.transit.Transit;
059
060 import net.dpml.util.Resolver;
061
062
063 /**
064 * Implementation of a resource.
065 *
066 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
067 * @version 1.1.0
068 */
069 public class DefaultResource extends DefaultDictionary implements Resource, Resolver, Comparable
070 {
071 private static final String LEGACY_DECIMAL_PREFIX_KEY =
072 "project.version-prefix.enabled";
073
074 /**
075 * Timestamp.
076 */
077 public static final String TIMESTAMP = getTimestamp();
078
079 /**
080 * Constant SNAPSHOT symbol.
081 */
082 public static final String SNAPSHOT = "SNAPSHOT";
083
084 /**
085 * Constant BOOTSTRAP symbol.
086 */
087 public static final String BOOTSTRAP = "BOOTSTRAP";
088
089 /**
090 * Constant RELEASE symbol.
091 */
092 public static final String RELEASE = "RELEASE";
093
094 /**
095 * Constant ANONYMOUS version symbol.
096 */
097 public static final String ANONYMOUS = "ANONYMOUS";
098
099 private final DefaultLibrary m_library;
100 private final ResourceDirective m_directive;
101 private final DefaultModule m_parent;
102 private final Type[] m_types;
103 private final String[] m_typeNames;
104 private final String m_path;
105 private final File m_basedir;
106 private final Map m_filters = new Hashtable();
107 private final Data[] m_data;
108
109 /**
110 * Creation of a new default resource.
111 * @param logger the assigned logging channel
112 * @param library the reference library
113 * @param directive the directive
114 */
115 DefaultResource( final DefaultLibrary library, final AbstractDirective directive )
116 {
117 super( null, directive );
118
119 m_library = library;
120 m_directive = null;
121 m_parent = null;
122 m_types = new Type[0];
123 m_typeNames = new String[0];
124 m_path = "";
125 m_basedir = null;
126 m_data = new Data[0];
127 }
128
129 /**
130 * Creation of a new default resource.
131 * @param logger the assigned logging channel
132 * @param library the reference library
133 * @param module the parent module
134 * @param directive the resource directive
135 */
136 DefaultResource(
137 final DefaultLibrary library, final DefaultModule module, final ResourceDirective directive )
138 {
139 super( module, directive );
140 if( null == directive )
141 {
142 throw new NullPointerException( "directive" );
143 }
144
145 m_library = library;
146 m_directive = directive;
147 m_parent = module;
148
149 if( module.isRoot() )
150 {
151 m_path = directive.getName();
152 }
153 else
154 {
155 m_path = module.getResourcePath() + "/" + directive.getName();
156 }
157
158 // setup produced types
159
160 m_types = directive.getTypeDirectives();
161 m_typeNames = new String[ m_types.length ];
162 for( int i=0; i<m_types.length; i++ )
163 {
164 Type type = m_types[i];
165 m_typeNames[i] = type.getID();
166 }
167
168 // setup production data
169
170 m_data = new Data[0];
171
172 // setup the resource basedir
173
174 File anchor = getAnchor();
175 String filename = m_directive.getBasedir();
176 if( null != filename )
177 {
178 String spec = resolve( filename );
179 File file = new File( spec );
180 if( file.isAbsolute() )
181 {
182 m_basedir = getCanonicalFile( file );
183 }
184 else
185 {
186 File basedir = new File( anchor, spec );
187 m_basedir = getCanonicalFile( basedir );
188 setProperty( "basedir", m_basedir.toString() );
189 }
190 }
191 else
192 {
193 if( !m_directive.getClassifier().equals( Classifier.LOCAL ) )
194 {
195 m_basedir = null;
196 }
197 else
198 {
199 final String error =
200 "Missing base directory declaration in resource ["
201 + m_path
202 + "].";
203 throw new ValidationException( error );
204 }
205 }
206
207 // setup the default properties
208
209 setProperty( "project.name", getName() );
210 if( null != m_parent )
211 {
212 setProperty( "project.group", m_parent.getResourcePath() );
213 }
214 else
215 {
216 setProperty( "project.group", "" );
217 }
218 String version = getVersion();
219 if( null != version )
220 {
221 setProperty( "project.version", version );
222 }
223
224 // setup filters
225
226 FilterDirective[] filters = directive.getFilterDirectives();
227 for( int i=0; i<filters.length; i++ )
228 {
229 FilterDirective filter = filters[i];
230 String token = filter.getToken();
231 m_filters.put( token, filter );
232 }
233 }
234
235 //----------------------------------------------------------------------------
236 // Resource
237 //----------------------------------------------------------------------------
238
239 /**
240 * Return a data directives.
241 * @return the associated production data
242 */
243 public Data[] getData()
244 {
245 return m_data;
246 }
247
248 /**
249 * Return the singleton library.
250 * @return the library
251 */
252 public Library getLibrary()
253 {
254 return m_library;
255 }
256
257 /**
258 * Return the name of the resource.
259 * @return the resource name
260 */
261 public String getName()
262 {
263 if( null != m_directive )
264 {
265 return m_directive.getName();
266 }
267 else
268 {
269 return null;
270 }
271 }
272
273 /**
274 * Return the resource version.
275 * @return the version
276 */
277 public String getVersion()
278 {
279 String version = getStatutoryVersion();
280 if( null != version )
281 {
282 return version;
283 }
284 else
285 {
286 return getStandardVersion();
287 }
288 }
289
290 /**
291 * Return the declard resource version.
292 * @return the statutory version
293 */
294 public String getStatutoryVersion()
295 {
296 if( null == m_directive )
297 {
298 return null;
299 }
300 else
301 {
302 String version = m_directive.getVersion();
303 if( null != version )
304 {
305 return version;
306 }
307 else
308 {
309 if( null != m_parent )
310 {
311 return m_parent.getStatutoryVersion();
312 }
313 else if( !m_directive.getClassifier().equals( Classifier.LOCAL ) )
314 {
315 return ANONYMOUS;
316 }
317 else
318 {
319 return null;
320 }
321 }
322 }
323 }
324
325 /**
326 * Return the decimal version.
327 *
328 * @return the version
329 */
330 public Version getDecimalVersion()
331 {
332 int major = getMajorVersion();
333 int minor = getMinorVersion();
334 int micro = getMicroVersion();
335 return new Version( major, minor, micro );
336 }
337
338 /**
339 * Return the fully qualified path to the resource.
340 * @return the path
341 */
342 public String getResourcePath()
343 {
344 return m_path;
345 }
346
347 /**
348 * Return the basedir for this resource.
349 * @return the base directory (possibly null)
350 */
351 public File getBaseDir()
352 {
353 return m_basedir;
354 }
355
356 /**
357 * Return the resource classifier.
358 * @return the classifier (LOCAL, EXTERNAL or ANONYMOUS)
359 */
360 public Classifier getClassifier()
361 {
362 if( null != m_directive )
363 {
364 return m_directive.getClassifier();
365 }
366 else
367 {
368 return ResourceDirective.ANONYMOUS;
369 }
370 }
371
372 /**
373 * Return the info block.
374 * @return the info block
375 */
376 public Info getInfo()
377 {
378 return m_directive.getInfoDirective();
379 }
380
381 /**
382 * Return the expanded array of types associated with the resource.
383 * The returned array is a function of the types declared by a resource
384 * expanded relative to any types implied by processor dependencies.
385 * @return the type array
386 */
387 public Type[] getTypes()
388 {
389 return m_types;
390 }
391
392 /**
393 * Test if this resource is associated with a type of the supplied name.
394 * @param type the type id
395 * @return TRUE if this resource produces an artifact of the supplied type
396 */
397 public boolean isa( final String type )
398 {
399 for( int i=0; i<m_types.length; i++ )
400 {
401 Type someType = m_types[i];
402 String name = someType.getID();
403 if( name.equals( type ) )
404 {
405 return true;
406 }
407 }
408 return false;
409 }
410
411 /**
412 * Return a resource type relative to a supplied type id.
413 * @param id the type name to retrieve
414 * @return the type instance
415 * @exception IllegalArgumentException if the id value does not match
416 * a type produced by the resource.
417 */
418 public Type getType( final String id ) throws IllegalArgumentException
419 {
420 for( int i=0; i<m_types.length; i++ )
421 {
422 Type type = m_types[i];
423 if( type.getID().equals( id ) )
424 {
425 return type;
426 }
427 }
428 final String error =
429 "Type name ["
430 + id
431 + "] not recognized with the scope of resource ["
432 + getResourcePath()
433 + "].";
434 throw new IllegalArgumentException( error );
435 }
436
437 /**
438 * Construct an link artifact for the supplied type.
439 * @param id the resource type id
440 * @return the link artifact
441 */
442 public Artifact getLinkArtifact( final String id )
443 {
444 if( null == m_directive )
445 {
446 final String error =
447 "Method not supported on virtual root.";
448 throw new UnsupportedOperationException( error );
449 }
450 if( null == id )
451 {
452 throw new NullPointerException( "id" );
453 }
454 String group = getGroupName();
455 String name = getName();
456 Type type = getType( id );
457 Version version = type.getVersion();
458 if( null == version )
459 {
460 final String error =
461 "Resource does not declare production of an alias for the requested type."
462 + "\nResource: " + this
463 + "\nType: " + id;
464 throw new IllegalArgumentException( error );
465 }
466 try
467 {
468 String spec = "link:" + id;
469 if( null != group )
470 {
471 spec = spec + ":" + group + "/" + name;
472 }
473 else
474 {
475 spec = spec + ":" + name;
476 }
477 if( !Version.NULL_VERSION.equals( version ) )
478 {
479 int major = version.getMajor();
480 int minor = version.getMinor();
481 spec = spec + "#"
482 + major
483 + "."
484 + minor;
485 }
486 return Artifact.createArtifact( spec );
487 }
488 catch( Throwable e )
489 {
490 final String error =
491 "Failed to construct link artifact for resource ["
492 + getResourcePath()
493 + "].";
494 throw new RuntimeException( error, e );
495 }
496 }
497
498 /**
499 * Construct an artifact for the supplied type.
500 * @param id the resource type identifier
501 * @return the artifact
502 */
503 public Artifact getArtifact( final String id )
504 {
505 if( null == m_directive )
506 {
507 final String error =
508 "Method not supported on virtual root.";
509 throw new UnsupportedOperationException( error );
510 }
511 if( null == id )
512 {
513 throw new NullPointerException( "id" );
514 }
515
516 String group = getGroupName();
517 String name = getName();
518 String version = getVersion();
519 String scheme = m_directive.getScheme();
520
521 try
522 {
523 return Artifact.createArtifact( scheme, group, name, version, id );
524 }
525 catch( Throwable e )
526 {
527 final String error =
528 "Failed to construct artifact for resource ["
529 + getResourcePath()
530 + "].";
531 throw new RuntimeException( error, e );
532 }
533 }
534
535 /**
536 * Return the enclosing parent module.
537 * @return the enclosing module of null if this a top-level module.
538 */
539 public Module getParent()
540 {
541 return getDefaultParent();
542 }
543
544 /**
545 * Return an array of filters associated with the resource.
546 * @return the array of filters
547 */
548 public Filter[] getFilters()
549 {
550 DefaultModule module = getDefaultParent();
551 if( null != module )
552 {
553 Map map = new Hashtable();
554 Filter[] filters = module.getFilters();
555 for( int i=0; i<filters.length; i++ )
556 {
557 Filter filter = filters[i];
558 String token = filter.getToken();
559 map.put( token, filter );
560 }
561 Filter[] local = getLocalFilters();
562 for( int i=0; i<local.length; i++ )
563 {
564 Filter filter = local[i];
565 String token = filter.getToken();
566 map.put( token, filter );
567 }
568 return (Filter[]) map.values().toArray( new Filter[0] );
569 }
570 else
571 {
572 return getLocalFilters();
573 }
574 }
575
576 //----------------------------------------------------------------------------
577 // Resolver
578 //----------------------------------------------------------------------------
579
580 /**
581 * Utility function supporting resolution of uris containing 'resource' or
582 * 'alias' schemes. If the supplied uri schem is 'resource' or 'alias' the
583 * reference is resolved to a artifact type, group and name from which a
584 * resource is resolved and the uri returned. If the scheme is resource
585 * the usri of the resource is returned. If the scheme is 'alias' a
586 * linkn alias is returned. If the scheme is not 'resource' or 'alias'
587 * the argument will be evaluated as a normal transit artifact uri
588 * specification.
589 *
590 * @param ref the uri argument
591 * @return the uri value
592 * @exception URISyntaxException if an error occurs during uri creation
593 */
594 public URI toURI( final String ref ) throws URISyntaxException
595 {
596 Artifact spec = Artifact.createArtifact( ref );
597 if( spec.isRecognized() )
598 {
599 return spec.toURI();
600 }
601 else if( ref.startsWith( "resource:" ) || ref.startsWith( "alias:" ) )
602 {
603 String type = spec.getType();
604 String group = spec.getGroup();
605 String name = spec.getName();
606 String path = group + "/" + name;
607 Library library = getLibrary();
608 try
609 {
610 Resource resource = library.getResource( path );
611 if( ref.startsWith( "resource:" ) )
612 {
613 Artifact artifact = resource.getArtifact( type );
614 return artifact.toURI();
615 }
616 else
617 {
618 Artifact artifact = resource.getLinkArtifact( type );
619 return artifact.toURI();
620 }
621 }
622 catch( ResourceNotFoundException e )
623 {
624 final String error =
625 "Unresolvable resource reference: " + path;
626 IllegalArgumentException iae = new IllegalArgumentException( error );
627 iae.initCause( e );
628 throw iae;
629 }
630 }
631 else
632 {
633 return spec.toURI();
634 }
635 }
636
637 //----------------------------------------------------------------------------
638 // implementation
639 //----------------------------------------------------------------------------
640
641 Map getFilterMap()
642 {
643 return m_filters;
644 }
645
646 Filter[] getLocalFilters()
647 {
648 return (Filter[]) getFilterMap().values().toArray( new Filter[0] );
649 }
650
651 /**
652 * Return an array of resource that are providers to this resource.
653 * @param scope the operational scope
654 * @param expand if true include transitive dependencies
655 * @param sort if true the array will sorted relative to dependencies
656 * @return the resource providers
657 */
658 public Resource[] getProviders( final Scope scope, final boolean expand, final boolean sort )
659 {
660 return getDefaultProviders( scope, expand, sort );
661 }
662
663 /**
664 * Return an array of resource that are providers to this resource. If
665 * the supplied scope is BUILD the returned resource array is equivalent
666 * <src>getProviders( Scope.BUILD, .. )</src>. If the scope is RUNTIME
667 * the returned resource array includes BUILD and RUNTIME resources. If
668 * the scope is TEST the returned array includes BUILD, RUNTIME and TEST
669 * resources.
670 * @param scope the scope of aggregation to be applied to the selection
671 * @param expand if TRUE include transitive dependencies
672 * @param sort if true the array will sorted relative to dependencies
673 * @return the resource providers
674 */
675 public Resource[] getAggregatedProviders( final Scope scope, final boolean expand, final boolean sort )
676 {
677 return getAggregatedDefaultProviders( scope, expand, sort, false );
678 }
679
680 /**
681 * Return a sorted and filtered array of providers. Resources not declaring
682 * the "jar" type as a produced type are excluded from selection. The
683 * resource array will include transitive dependencies. The method is
684 * suitable for the construction of build and test phase classloaders.
685 *
686 * @param scope the aggregation scope
687 * @return the scoped resource chain
688 */
689 public Resource[] getClasspathProviders( final Scope scope )
690 {
691 DefaultResource[] result = getAggregatedDefaultProviders( scope, true, true, true );
692 List stack = new ArrayList();
693 for( int i=0; i<result.length; i++ )
694 {
695 DefaultResource resource = result[i];
696 if( resource.isa( "jar" ) )
697 {
698 stack.add( resource );
699 }
700 }
701 return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
702 }
703
704 /**
705 * Return an array of runtime providers filtered relative to a supplied
706 * classloading category. Resources not declaring the "jar" type as a
707 * produced type are excluded from selection. The resource array returned
708 * from this operation is a sorted transitive sequence excluding all
709 * resource references by any category higher than the supplied category.
710 * This method is typically used to construct information suitable for
711 * the gerneration of plugin metadata.
712 *
713 * @param category the classloader category
714 * @return the category scoped resource chain
715 */
716 public Resource[] getClasspathProviders( final Category category )
717 {
718 DefaultResource[] resources = getClasspathDefaultProviders( category );
719 return sortDefaultResources( resources, Scope.RUNTIME );
720 }
721
722 /**
723 * Return an array of resources that are consumers of this resource.
724 * @param expand if true the returned array includes consumers associated
725 * through transitive dependency relationships, otherwise the array is
726 * limited to direct consumers
727 * @param sort if true the array is sorted relative to depenency relationships
728 * @return the array of consumer projects
729 */
730 public Resource[] getConsumers( final boolean expand, final boolean sort )
731 {
732 return getDefaultConsumers( expand, sort );
733 }
734
735 /**
736 * Return the underlying resource defintion.
737 * @return the resource directive
738 */
739 public ResourceDirective getResourceDirective()
740 {
741 return m_directive;
742 }
743
744 /**
745 * Return a filename using the layout strategy employed by the cache.
746 * @param id the artifact type
747 * @return the filename
748 */
749 public String getLayoutPath( final String id )
750 {
751 Artifact artifact = getArtifact( id );
752 return Transit.getInstance().getCacheLayout().resolveFilename( artifact );
753 }
754
755 /**
756 * Return a directive suitable for publication as an external description.
757 * @param module the enclosing module
758 * @return the resource directive
759 */
760 ResourceDirective exportResource( final DefaultModule module )
761 {
762 if( null == m_directive )
763 {
764 final String error =
765 "Cannot export from the root module.";
766 throw new UnsupportedOperationException( error );
767 }
768 String name = getName();
769 String version = getVersion();
770 String basedir = null;
771 InfoDirective info = m_directive.getInfoDirective();
772 TypeDirective[] types = m_directive.getTypeDirectives();
773 TypeDirective[] exportedTypes = createExportedTypes( types );
774 DependencyDirective[] dependencies = createDeps( module );
775 Properties properties = getExportProperties();
776 return ResourceDirective.createResourceDirective(
777 name, version, Classifier.EXTERNAL, basedir,
778 info, exportedTypes, dependencies, properties, null );
779 }
780
781 TypeDirective[] createExportedTypes( final TypeDirective[] types )
782 {
783 TypeDirective[] export = new TypeDirective[ types.length ];
784 for( int i=0; i<export.length; i++ )
785 {
786 TypeDirective type = types[i];
787 String id = type.getID();
788 Version version = type.getVersion();
789 export[i] = new TypeDirective( id, version );
790 }
791 return export;
792 }
793
794 private DependencyDirective[] createDeps( final DefaultModule module )
795 {
796 ArrayList list = new ArrayList();
797 createIncludeDirectives( module, list, Category.SYSTEM );
798 createIncludeDirectives( module, list, Category.PUBLIC );
799 createIncludeDirectives( module, list, Category.PROTECTED );
800 createIncludeDirectives( module, list, Category.PRIVATE );
801 if( list.size() == 0 )
802 {
803 return new DependencyDirective[0];
804 }
805 else
806 {
807 IncludeDirective[] includes =
808 (IncludeDirective[]) list.toArray( new IncludeDirective[0] );
809 DependencyDirective runtime =
810 new DependencyDirective( Scope.RUNTIME, includes );
811 return new DependencyDirective[]{runtime};
812 }
813 }
814
815 boolean isaDescendant( final DefaultModule module )
816 {
817 if( module == this )
818 {
819 return true;
820 }
821 if( m_parent == null )
822 {
823 return false;
824 }
825 else
826 {
827 if( m_parent == module )
828 {
829 return true;
830 }
831 else
832 {
833 return m_parent.isaDescendant( module );
834 }
835 }
836 }
837
838 private void createIncludeDirectives(
839 final DefaultModule module, final List list, final Category category )
840 {
841 DefaultResource[] providers =
842 getDefaultProviders( Scope.RUNTIME, true, category );
843 for( int i=0; i<providers.length; i++ )
844 {
845 DefaultResource provider = providers[i];
846 if( provider.isaDescendant( module ) )
847 {
848 // create a ref
849 String path = provider.getResourcePath();
850 IncludeDirective include =
851 new IncludeDirective(
852 IncludeDirective.REF,
853 category,
854 path,
855 null );
856 list.add( include );
857 }
858 else
859 {
860 // create a urn
861
862 Type[] types = provider.getTypes();
863 for( int j=0; j<types.length; j++ )
864 {
865 Type type = types[j];
866 String label = type.getID();
867 Artifact artifact = provider.getArtifact( label );
868 String urn = artifact.toString();
869 IncludeDirective include =
870 new IncludeDirective(
871 IncludeDirective.URI,
872 category,
873 urn,
874 null );
875 list.add( include );
876 }
877 }
878 }
879 }
880
881 //----------------------------------------------------------------------------
882 // Object
883 //----------------------------------------------------------------------------
884
885 /**
886 * Return a string representation of the resource in the form 'resource:[path]'.
887 * @return the string value
888 */
889 public String toString()
890 {
891 if( null != m_directive )
892 {
893 if( m_directive.isLocal() )
894 {
895 return toString( "project" );
896 }
897 }
898 return toString( "resource" );
899 }
900
901 String toString( final String type )
902 {
903 return type + ":" + getResourcePath() + "#" + getVersion();
904 }
905
906 /**
907 * Compare this object with another.
908 * @param other the other object
909 * @return the comparitive index
910 */
911 public int compareTo( final Object other )
912 {
913 if( other instanceof DefaultResource )
914 {
915 DefaultResource resource = (DefaultResource) other;
916 return getResourcePath().compareTo( resource.m_path );
917 }
918 else
919 {
920 return -1;
921 }
922 }
923
924 //----------------------------------------------------------------------------
925 // internals
926 //----------------------------------------------------------------------------
927
928 /**
929 * Return the singlton library.
930 * @return the library
931 */
932 DefaultLibrary getDefaultLibrary()
933 {
934 return m_library;
935 }
936
937 boolean isAnonymous()
938 {
939 if( null != m_directive )
940 {
941 return m_directive.isAnonymous();
942 }
943 return false;
944 }
945
946 boolean isLocal()
947 {
948 if( null != m_directive )
949 {
950 return m_directive.isLocal();
951 }
952 return false;
953 }
954
955 DefaultModule getDefaultParent()
956 {
957 if( null != m_parent )
958 {
959 if( m_parent.isRoot() )
960 {
961 return null;
962 }
963 }
964 return m_parent;
965 }
966
967 DefaultResource[] getAggregatedDefaultProviders(
968 final Scope scope, final boolean expanded, final boolean sort, final boolean flag )
969 {
970 DefaultResource[] resources =
971 getAggregatedDefaultProviders( scope, expanded, flag );
972 if( sort )
973 {
974 return sortDefaultResources( resources, scope );
975 }
976 else
977 {
978 Arrays.sort( resources );
979 return resources;
980 }
981 }
982
983 DefaultResource[] getAggregatedDefaultProviders(
984 final Scope scope, final boolean expanded, final boolean flag )
985 {
986 ArrayList list = new ArrayList();
987 if( !flag )
988 {
989 aggregateProviders( list, Scope.BUILD );
990 }
991 if( scope.isGreaterThan( Scope.BUILD ) )
992 {
993 aggregateProviders( list, Scope.RUNTIME );
994 }
995 if( scope.isGreaterThan( Scope.RUNTIME ) )
996 {
997 aggregateProviders( list, Scope.TEST );
998 }
999 DefaultResource[] result = (DefaultResource[]) list.toArray( new DefaultResource[0] );
1000 if( expanded )
1001 {
1002 List visited = new ArrayList();
1003 List stack = new ArrayList();
1004 for( int i=0; i<result.length; i++ )
1005 {
1006 DefaultResource resource = result[i];
1007 expandDefaultResource( visited, stack, scope, resource );
1008 }
1009 result = (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1010 }
1011 return result;
1012 }
1013
1014 private void aggregateProviders( final List list, final Scope scope )
1015 {
1016 DefaultResource[] resources = getDefaultProviders( scope, false, null );
1017 for( int i=0; i<resources.length; i++ )
1018 {
1019 DefaultResource resource = resources[i];
1020 if( !list.contains( resource ) )
1021 {
1022 list.add( resource );
1023 }
1024 }
1025 }
1026
1027 DefaultResource[] getDefaultProviders(
1028 final Scope scope, final boolean expanded, final boolean sort )
1029 {
1030 DefaultResource[] resources = getDefaultProviders( scope, expanded, null );
1031 if( sort )
1032 {
1033 return sortDefaultResources( resources, scope );
1034 }
1035 else
1036 {
1037 Arrays.sort( resources );
1038 return resources;
1039 }
1040 }
1041
1042 DefaultResource[] getDefaultProviders(
1043 final Scope scope, final boolean expand, final Category category )
1044 {
1045 ArrayList visited = new ArrayList();
1046 ArrayList stack = new ArrayList();
1047 DefaultResource[] providers = getLocalDefaultProviders( scope, category );
1048 for( int i=0; i<providers.length; i++ )
1049 {
1050 DefaultResource provider = providers[i];
1051 if( expand )
1052 {
1053 expandDefaultResource( visited, stack, scope, provider );
1054 }
1055 else if( !stack.contains( provider ) )
1056 {
1057 stack.add( provider );
1058 }
1059 }
1060 return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1061 }
1062
1063 DefaultResource[] getLocalDefaultProviders( final Scope scope, final Category category )
1064 {
1065 if( null == m_directive )
1066 {
1067 return new DefaultResource[0];
1068 }
1069 IncludeDirective[] includes = getLocalIncludes( scope, category );
1070 DefaultResource[] resources = new DefaultResource[ includes.length ];
1071 for( int i=0; i<includes.length; i++ )
1072 {
1073 IncludeDirective include = includes[i];
1074 if( include.getMode().equals( IncludeDirective.URI ) )
1075 {
1076 try
1077 {
1078 String value = include.getValue();
1079 String urn = resolve( value );
1080 Properties properties = include.getProperties();
1081 resources[i] = m_library.getAnonymousResource( urn, properties );
1082 }
1083 catch( URISyntaxException e )
1084 {
1085 final String error =
1086 "Invalid uri value: " + include.getValue();
1087 throw new RuntimeException( error, e );
1088 }
1089 catch( InvalidNameException e )
1090 {
1091 final String error =
1092 "An anonomous dependency include reference to ["
1093 + include
1094 + "] within the resource ["
1095 + getResourcePath()
1096 + "] could not be resolved.";
1097 throw new InvalidNameException( error, e );
1098 }
1099 catch( Exception e )
1100 {
1101 final String error =
1102 "Unexpected error during dynamic resource creation.";
1103 throw new RuntimeException( error, e );
1104 }
1105 }
1106 else
1107 {
1108 String ref = getIncludeReference( include );
1109 try
1110 {
1111 DefaultResource resource = m_library.getDefaultResource( ref );
1112 resources[i] = resource;
1113 }
1114 catch( InvalidNameException e )
1115 {
1116 if( null == category )
1117 {
1118 final String error =
1119 "A dependency include ["
1120 + ref
1121 + "] within ["
1122 + this
1123 + "] referencing ["
1124 + ref
1125 + "] under the scope ["
1126 + scope
1127 + "] is unknown.";
1128 throw new InvalidNameException( error );
1129 }
1130 else
1131 {
1132 final String error =
1133 "A dependency include within ["
1134 + this
1135 + "] referencing ["
1136 + ref
1137 + "] under the scope ["
1138 + scope
1139 + "] and category ["
1140 + category
1141 + "] is unknown.";
1142 throw new InvalidNameException( error );
1143 }
1144 }
1145 }
1146 }
1147 return resources;
1148 }
1149
1150 private IncludeDirective[] getLocalIncludes( final Scope scope, final Category category )
1151 {
1152 DependencyDirective dependency = m_directive.getDependencyDirective( scope );
1153 if( null == category )
1154 {
1155 return dependency.getIncludeDirectives();
1156 }
1157 else
1158 {
1159 return dependency.getIncludeDirectives( category );
1160 }
1161 }
1162
1163 private void expandDefaultResource(
1164 final List visited, final List stack, final Scope scope, final DefaultResource resource )
1165 {
1166 if( visited.contains( resource ) )
1167 {
1168 return;
1169 }
1170 else
1171 {
1172 visited.add( resource );
1173 boolean flag = !scope.equals( Scope.BUILD );
1174 DefaultResource[] providers = resource.getAggregatedDefaultProviders( scope, false, flag );
1175 for( int i=0; i<providers.length; i++ )
1176 {
1177 DefaultResource provider = providers[i];
1178 expandDefaultResource( visited, stack, scope, provider );
1179 }
1180 stack.add( resource );
1181 }
1182 }
1183
1184 private String getIncludeReference( final IncludeDirective directive )
1185 {
1186 if( null == m_parent )
1187 {
1188 return directive.getValue();
1189 }
1190 else
1191 {
1192 if( IncludeDirective.REF.equals( directive.getMode() ) )
1193 {
1194 return directive.getValue();
1195 }
1196 else
1197 {
1198 String path = m_parent.getResourcePath();
1199 if( "".equals( path ) )
1200 {
1201 return directive.getValue();
1202 }
1203 else
1204 {
1205 String key = directive.getValue();
1206 return path + "/" + key;
1207 }
1208 }
1209 }
1210 }
1211
1212 //----------------------------------------------------------------------------
1213 // consumer concerns
1214 //----------------------------------------------------------------------------
1215
1216 boolean isaConsumer( final DefaultResource resource )
1217 {
1218 DefaultResource[] resources = getAggregatedDefaultProviders( Scope.TEST, false, false );
1219 for( int i=0; i<resources.length; i++ )
1220 {
1221 DefaultResource provider = resources[i];
1222 if( resource.equals( provider ) )
1223 {
1224 return true;
1225 }
1226 }
1227 return false;
1228 }
1229
1230 DefaultResource[] getDefaultConsumers( final boolean expand, final boolean sort )
1231 {
1232 DefaultResource[] consumers = getDefaultConsumers( expand );
1233 if( sort )
1234 {
1235 return sortDefaultResources( consumers, Scope.TEST );
1236 }
1237 else
1238 {
1239 return consumers;
1240 }
1241 }
1242
1243 DefaultResource[] getDefaultConsumers( final boolean expand )
1244 {
1245 if( !expand )
1246 {
1247 ArrayList list = new ArrayList();
1248 DefaultResource[] resources = m_library.selectDefaultResources( "**/*" );
1249 for( int i=0; i<resources.length; i++ )
1250 {
1251 DefaultResource resource = resources[i];
1252 if( !list.contains( resource ) && resource.isaConsumer( this ) )
1253 {
1254 list.add( resource );
1255 }
1256 }
1257 return (DefaultResource[]) list.toArray( new DefaultResource[0] );
1258 }
1259 else
1260 {
1261 ArrayList visited = new ArrayList();
1262 ArrayList stack = new ArrayList();
1263 DefaultResource[] consumers = getDefaultConsumers( false );
1264 for( int i=0; i<consumers.length; i++ )
1265 {
1266 DefaultResource consumer = consumers[i];
1267 processConsumer( visited, stack, consumer );
1268 }
1269 return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1270 }
1271 }
1272
1273 void processConsumer( final List visited, final List stack, final DefaultResource consumer )
1274 {
1275 if( visited.contains( consumer ) )
1276 {
1277 return;
1278 }
1279 visited.add( consumer );
1280 stack.add( consumer );
1281 DefaultResource[] resources = consumer.getDefaultConsumers( false, false );
1282 for( int i=0; i<resources.length; i++ )
1283 {
1284 DefaultResource resource = resources[i];
1285 processConsumer( visited, stack, resource );
1286 }
1287 }
1288
1289 //----------------------------------------------------------------------------
1290 // classpath stuff
1291 //----------------------------------------------------------------------------
1292
1293 /**
1294 * Construct an array of resources based on the RUNTIME scoped dependencies
1295 * associated with the supplied category. The implementation builds a list
1296 * of all preceeding categories as a basis for filtering the returned list ensuring
1297 * no duplicate references are returned.
1298 * @param category the runtime classloader category
1299 * @return the array of resources the define a classloader for the category
1300 */
1301 private DefaultResource[] getClasspathDefaultProviders( final Category category )
1302 {
1303 ArrayList list = new ArrayList();
1304 for( int i=0; i<category.getValue(); i++ )
1305 {
1306 Category c = Category.parse( i );
1307 DefaultResource[] collection =
1308 getDefaultProviders( Scope.RUNTIME, true, c );
1309 for( int j=0; j<collection.length; j++ )
1310 {
1311 list.add( collection[j] );
1312 }
1313 }
1314 DefaultResource[] resources =
1315 getDefaultProviders( Scope.RUNTIME, true, category );
1316 ArrayList stack = new ArrayList();
1317 for( int i=0; i<resources.length; i++ )
1318 {
1319 DefaultResource resource = resources[i];
1320 if( resource.isa( "jar" ) && !list.contains( resource ) )
1321 {
1322 stack.add( resource );
1323 }
1324 }
1325
1326 return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1327 }
1328
1329 //----------------------------------------------------------------------------
1330 // sorting relative to dependencies
1331 //----------------------------------------------------------------------------
1332
1333 DefaultResource[] sortDefaultResources( final DefaultResource[] resources )
1334 {
1335 return sortDefaultResources( resources, Scope.TEST );
1336 }
1337
1338 DefaultResource[] sortDefaultResources( final DefaultResource[] resources, final Scope scope )
1339 {
1340 ArrayList visited = new ArrayList();
1341 ArrayList stack = new ArrayList();
1342 for( int i=0; i<resources.length; i++ )
1343 {
1344 DefaultResource resource = resources[i];
1345 resource.sortDefaultResource( visited, stack, scope, resources );
1346 }
1347 return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1348 }
1349
1350 void sortDefaultResource(
1351 final List visited, final List stack, final Scope scope, final DefaultResource[] resources )
1352 {
1353 if( visited.contains( this ) )
1354 {
1355 return;
1356 }
1357 else
1358 {
1359 visited.add( this );
1360 DefaultResource[] providers =
1361 getAggregatedDefaultProviders( scope, false, false );
1362 for( int i=0; i<providers.length; i++ )
1363 {
1364 DefaultResource provider = providers[i];
1365 if( isaMember( resources, provider ) )
1366 {
1367 provider.sortDefaultResource( visited, stack, scope, resources );
1368 }
1369 }
1370 if( !stack.contains( this ) )
1371 {
1372 stack.add( this );
1373 }
1374 }
1375 }
1376
1377 boolean isaMember( final DefaultResource[] resources, final DefaultResource resource )
1378 {
1379 for( int i=0; i<resources.length; i++ )
1380 {
1381 DefaultResource r = resources[i];
1382 if( resource == r )
1383 {
1384 return true;
1385 }
1386 }
1387 return false;
1388 }
1389
1390 //----------------------------------------------------------------------------
1391 // version utilities
1392 //----------------------------------------------------------------------------
1393
1394 private String getStandardVersion()
1395 {
1396 String signature = getBuildSignature();
1397 if( BOOTSTRAP.equals( signature ) )
1398 {
1399 return BOOTSTRAP;
1400 }
1401
1402 if( isDecimal() )
1403 {
1404 Version decimal = getDecimalVersion();
1405 String spec = decimal.toString();
1406 if( null == signature )
1407 {
1408 return spec;
1409 }
1410 else
1411 {
1412 return spec + "-" + signature;
1413 }
1414 }
1415 else
1416 {
1417 if( null == signature )
1418 {
1419 return SNAPSHOT;
1420 }
1421 else
1422 {
1423 return signature;
1424 }
1425 }
1426 }
1427
1428 private boolean isDecimal()
1429 {
1430 boolean isDecimal = Boolean.getBoolean( DECIMAL_VERSIONING_KEY );
1431 isDecimal = getBooleanProperty( DECIMAL_VERSIONING_KEY, isDecimal );
1432 return getBooleanProperty( LEGACY_DECIMAL_PREFIX_KEY, isDecimal );
1433 }
1434
1435 private String getBuildSignature()
1436 {
1437 String system = System.getProperty( "build.signature", null );
1438 return getProperty( "build.signature", system );
1439 }
1440
1441 /*
1442 private String getBuildSignature()
1443 {
1444 String system = System.getProperty( "build.signature", null );
1445 String value = getProperty( "build.signature", system );
1446 if( null == value )
1447 {
1448 return SNAPSHOT;
1449 }
1450 else if( value.equals( "project.timestamp" ) )
1451 {
1452 return TIMESTAMP;
1453 }
1454 else
1455 {
1456 return value;
1457 }
1458 }
1459 */
1460
1461 private int getMajorVersion()
1462 {
1463 return getIntegerProperty( "project.major.version", 0 );
1464 }
1465
1466 private int getMinorVersion()
1467 {
1468 return getIntegerProperty( "project.minor.version", 0 );
1469 }
1470
1471 private int getMicroVersion()
1472 {
1473 return getIntegerProperty( "project.micro.version", 0 );
1474 }
1475
1476 /**
1477 * Return the UTC YYMMDD.HHMMSSS signature of a date.
1478 * @return the UTC date-stamp signature
1479 */
1480 public static String getTimestamp()
1481 {
1482 return getTimestamp( new Date() );
1483 }
1484
1485 /**
1486 * Return the UTC YYMMDD.HHMMSSS signature of a date.
1487 * @param date the date
1488 * @return the UTC date-stamp signature
1489 */
1490 public static String getTimestamp( final Date date )
1491 {
1492 final SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
1493 sdf.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
1494 return sdf.format( date );
1495 }
1496
1497 //----------------------------------------------------------------------------
1498 // other utilities
1499 //----------------------------------------------------------------------------
1500
1501 private File getAnchor()
1502 {
1503 if( null != m_parent )
1504 {
1505 File anchor = m_parent.getBaseDir();
1506 if( null != anchor )
1507 {
1508 return anchor;
1509 }
1510 }
1511 return m_library.getRootDirectory();
1512 }
1513
1514 File getCanonicalFile( final File file )
1515 {
1516 try
1517 {
1518 return file.getCanonicalFile();
1519 }
1520 catch( IOException e )
1521 {
1522 final String error =
1523 "internal error while attempting to convert the file ["
1524 + file
1525 + "] to its canonical representation.";
1526 throw new RuntimeException( error, e );
1527 }
1528 }
1529
1530 private String getGroupName()
1531 {
1532 if( m_parent.isRoot() )
1533 {
1534 return null;
1535 }
1536 else
1537 {
1538 return m_parent.getResourcePath();
1539 }
1540 }
1541 }